# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# (C) 2012 Remek Zajac
#!/usr/bin/python

import threading
import time
import sys

class Singleton:
    threadLock = threading.Lock()
    """
    A non-thread-safe helper class to ease implementing singletons.
    This should be used as a decorator -- not a metaclass -- to the
    class that should be a singleton.

    The decorated class can define one `__init__` function that
    takes only the `self` argument. Other than that, there are
    no restrictions that apply to the decorated class.

    To get the singleton instance, use the `Instance` method. Trying
    to use `__call__` will result in a `TypeError` being raised.

    Limitations: The decorated class cannot be inherited from.

    """

    def __init__(self, decorated):
        self._decorated = decorated
        self._instance = None

    def Instance(self):
        """
        Returns the singleton instance. Upon its first call, it creates a
        new instance of the decorated class and calls its `__init__` method.
        On all subsequent calls, the already created instance is returned.
        """
        self.threadLock.acquire()
        if not self._instance:
            self._instance = self._decorated()
        self.threadLock.release()
        return self._instance

    def __call__(self):
        raise TypeError('Singletons must be accessed through `Instance()`.')

    def __instancecheck__(self, inst):
        return isinstance(inst, self._decorated)


class DebugTimeReporter:
    def __init__(self, name, parent=None):
        self.nestedTimeReporters = []
        self.name = name
        if parent:
            parent.nestedTimeReporters.append(self)
        self.parent = parent
        self.accumulatedTime = 0
        self.startTime = None

    def start(self):
        self.startTime = time.time()

    def stop(self):
        stopTime = time.time()
        assert(self.startTime)
        self.accumulatedTime += stopTime - self.startTime
        self.startTime = None

    def measure(self, op):
        self.start()
        result = op()
        self.stop()
        return result

    def timeSpent(self):
        return self.accumulatedTime

    def reportIndentation(self):
        if self.parent:
            return self.parent.reportIndentation()+'  '
        return ''

    def report(self, output = sys.stdout):
        assert(not self.startTime)
        if not self.parent:
            output.write("TIME REPORT *******************************************\n")
        output.write(self.reportIndentation()+'Timer:'+self.name+' total:'+('%0.2f'%self.accumulatedTime)+'s\n')
        contributions = 0
        for report in self.nestedTimeReporters:
            proportion = report.timeSpent()/self.timeSpent()
            contributions += report.timeSpent()
            output.write(self.reportIndentation()+' *'+str(int(proportion*100))+'%\n')
            report.report(output)
        if len(self.nestedTimeReporters) > 0:
            unacountedFor = self.timeSpent()-contributions
            proportion = unacountedFor/self.timeSpent()        
            output.write(self.reportIndentation()+ ' *Unaccounted for:'+('%0.2f'%unacountedFor)+'s, representing:'+str(int(proportion*100))+'%\n')
        if not self.parent:
            output.write("*******************************************************\n")
            
        
